/*

This file is part of Rooms3D.

Copyright (C) 2008 Pixecur Technologies Incorporated (Esotera).
Visit Esotera at http://www.esoteras3.com
 
Rooms3D is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
 
Rooms3D is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Rooms3D; see the file COPYING.  If not, write to 
Pixecur Technologies, 213-31 Peet Street, St. John's, NL, A1B 3W8. 
 
Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
 
 */
package com.datasentinel.rooms.ui.sh3d.roomeditor;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.logging.Logger;

import com.datasentinel.file.FileBuilderFactory;
import com.datasentinel.file.FileMapping;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.HomeRecorder;
import com.eteks.sweethome3d.model.RecorderException;
import com.eteks.sweethome3d.tools.ResourceURLContent;

/**
 * @author rickp
 * 
 */
public class CustomHomeFileRecorder implements HomeRecorder
{
	public static Logger logger = Logger.getLogger(CustomHomeFileRecorder.class.getName());
	public static String OPAQUE_RESOURCE_URL = "file://datasentinel.com/rooms3d/classpath/";

	public CustomHomeFileRecorder()
	{
		super();
	}

	@Override
	public boolean exists(String name) throws RecorderException
	{
		return new File(name).exists();
	}

	@Override
	public Home readHome(String name) throws RecorderException
	{
		Home home = null;
		FileInputStream fis = null;
		try
		{
			File homeFile = new File(name);
			fis = new FileInputStream(homeFile);
			home = (Home)readHome(fis);
		}
		catch (IOException e)
		{
			throw new RecorderException(e.toString());
		}
		finally
		{
			closeIfSet(fis);
		}
		return home;
	}

	@Override
	public void writeHome(Home home, String name) throws RecorderException
	{
		File permSave = null;
		File tempSave = null;
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try
		{
			permSave = new File(name);
			tempSave = File.createTempFile("save", ".sh3d");
			FileOutputStream tempSaveOut = new FileOutputStream(tempSave);
			writeHome(home, tempSaveOut);
			if (!tempSave.renameTo(permSave))
			{
				// rename failed for some reason, copy manually, :(
				fis = new FileInputStream(tempSave);
				fos = new FileOutputStream(permSave);
				byte[] buf = new byte[8192];
				int bytesRead = 0;
				while (-1 != (bytesRead = fis.read(buf)))
				{
					fos.write(buf, 0, bytesRead);
				}
			}
		}
		catch (IOException e)
		{
			throw new RecorderException(e.toString());
		}
		finally
		{
			closeIfSet(fis);
			closeIfSet(fos);
			if (tempSave != null)
			{
				tempSave.delete();
			}
		}
	}

	public Home readHome(InputStream instream) throws RecorderException
	{
		Home home = null;
		ObjectInputStream ois = null;
		try
		{
			ois = new CustomObjectInputStream(instream);
			home = (Home)ois.readObject();
		}
		catch (Exception e)
		{
			throw new RecorderException(e.toString());
		}
		finally
		{
			closeIfSet(ois);
		}
		return home;
	}

	public void writeHome(Home home, OutputStream ostream) throws RecorderException
	{
		ObjectOutputStream oos = null;
		try
		{
			oos = new CustomObjectOutputStream(ostream);
			oos.writeObject(home);
		}
		catch (Exception e)
		{
			throw new RecorderException(e.toString());
		}
		finally
		{
			closeIfSet(oos);
		}
	}

	private void closeIfSet(Closeable closeable)
	{
		if (closeable == null)
			return;
		try
		{
			closeable.close();
		}
		catch (IOException ioe)
		{
		}
	}

	private static class CustomObjectInputStream extends ObjectInputStream
	{
		public CustomObjectInputStream(InputStream istream) throws IOException
		{
			super(istream);
			enableResolveObject(true);
		}

		@Override
		public Object resolveObject(Object object) throws IOException
		{
			if (object instanceof FileMapping)
				try 
				{
					return FileBuilderFactory.createFileBuilder().unmap((FileMapping)object);
				} 
				catch (IOException e) 
				{
					return null;
				}
			if (object instanceof ResourceURLContent)
			{
				ResourceURLContent toReplace = (ResourceURLContent)object;
				URL resURL = toReplace.getURL();
				return new ResourceURLContent(resolveClasspathResource(resURL), toReplace
						.isMultiPartResource());
			}
			return object;
		}

		private URL resolveClasspathResource(URL resource) throws MalformedURLException
		{
			String resourceURLString = resource.toString();
			if (resourceURLString.startsWith(OPAQUE_RESOURCE_URL))
			{
				String fixedResourceURLStr = resourceURLString.substring(OPAQUE_RESOURCE_URL.length() - 1);
				logger.finer(String.format("looking up resource [%s] via classloader [%s]",fixedResourceURLStr,CustomHomeFileRecorder.class.getClassLoader()));
				
				URL fixedResource = CustomHomeFileRecorder.class.getResource(fixedResourceURLStr);
				if (null != fixedResource)
				{
					logger.fine("remapped " + resource + " as " + fixedResource);
					return fixedResource;
				}
			}
			return resource;
		}
	}

	private static class CustomObjectOutputStream extends ObjectOutputStream
	{
		public CustomObjectOutputStream(OutputStream ostream) throws IOException
		{
			super(ostream);
			enableReplaceObject(true);
		}

		@Override
		protected Object replaceObject(Object object) throws IOException
		{
			if (object instanceof File)
				return FileBuilderFactory.createFileBuilder().createMapping((File)object);
			if (object instanceof ResourceURLContent)
			{
				ResourceURLContent toReplace = (ResourceURLContent)object;
				return new ResourceURLContent(unresolveClasspathResource(toReplace
						.getURL()), toReplace.isMultiPartResource());
			}
			return object;
		}

		private URL unresolveClasspathResource(URL resource) throws MalformedURLException
		{
			URL fixedResource = resource;
			String resourceURLString = getClasspathResource(resource);
			if (null != resourceURLString)
			{
				String fixedURLString = OPAQUE_RESOURCE_URL + resourceURLString;
				fixedResource = new URL(fixedURLString);
				logger.fine("remapped " + resource + " as " + fixedResource);
			}
			return fixedResource;
		}
	}
	public static String getClasspathResource(URL resource) throws MalformedURLException
	{
		String resourceURLString = resource.toString();
		if ("jar".equals(resource.getProtocol()))
		{
			// don't care about anything before !/ which is 2 chars, hence the +2 below
			String unresolvedResource =  resourceURLString.substring(resourceURLString.indexOf("!/")+2);
			logger.fine(String.format("unresolved [%s] as [%s] from jar [%s]",
					resourceURLString, unresolvedResource, resourceURLString));
			return unresolvedResource;
		}
		else
		{
			URLClassLoader urlCL = (URLClassLoader)CustomHomeFileRecorder.class.getClassLoader();
			for (URL cpElement : urlCL.getURLs())
			{
				String cpElementString = cpElement.toString();
				if (0 == resourceURLString.indexOf(cpElementString))
				{
					String unresolvedResource = resourceURLString.substring(cpElementString
							.length());
					logger.fine(String.format("unresolved [%s] as [%s] via classloader [%s]",
							resourceURLString, unresolvedResource, urlCL));
					return unresolvedResource;
				}
			}
		}
		return null;
	}
}
